﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.IO;

namespace cs2335lab5
{
    namespace Server
    {
        /// <summary>
        /// This class manages a collection of issues on our server.
        /// </summary>
        [XmlRoot("IssueManager")]
        [XmlInclude(typeof(Data.Issue))]
        [XmlInclude(typeof(Data.User))]
        public class IssueManager
        {
            /// <summary>
            /// Serializeable variable for the counter
            /// </summary>
            public Int64 Counter { get; set; }

            /// <summary>
            /// Incrementing counter variable
            /// </summary>
            [XmlIgnore]
            public Int64 IncrementingCounter
            {
                get
                {
                    Counter++;
                    return Counter;
                }
            }

            /// <summary>
            /// A quick way to look up an issue by its id. Key=id, Value=issue object for that id
            /// </summary>
            private Dictionary<Int64?, Data.Issue> issues;

            /// <summary>
            /// Note that we cannot serialize a generic dictionary.
            /// </summary>
            [XmlIgnore]
            public Dictionary<Int64?, Data.Issue> Issues
            {
                get { return issues; }
                set { issues = value; }
            }

            /// <summary>
            /// Property created to fool xml serialization
            /// </summary>
            [XmlArray("Issues")]
            [XmlArrayItem("IssuesLine", Type = typeof(DictionaryEntry))]
            public DictionaryEntry[] UserArray
            {
                get
                {
                    //Make an array of DictionaryEntries to return 
                    DictionaryEntry[] ret = new DictionaryEntry[Issues.Count];
                    int i = 0;
                    DictionaryEntry de;
                    //Iterate through Stuff to load items into the array. 
                    foreach (KeyValuePair<Int64?, Data.Issue> issuesLine in Issues)
                    {
                        de = new DictionaryEntry();
                        de.Key = issuesLine.Key;
                        de.Value = issuesLine.Value;
                        ret[i] = de;
                        i++;
                    }
                    return ret;
                }
                set
                {
                    Issues.Clear();
                    for (int i = 0; i < value.Length; i++)
                    {
                        Issues.Add((Int64?)value[i].Key, (Data.Issue)value[i].Value);
                    }
                }
            }

            /// <summary>
            /// Constructor, makes a new instance of class
            /// </summary>
            public IssueManager()
            {
                issues = new Dictionary<Int64?, Data.Issue>();
            }

            /// <summary>
            /// Given an issue, add it to the collection of our issues
            /// </summary>
            /// <param name="issue">The issue object to add</param>
            /// <returns>true if that issue does not already exist, false otherwise</returns>
            public Data.Issue AddIssue(Data.User actor, Data.Issue issue)
            {
                // make sure issue isn't null
                if (issue == null)
                {
                    return null;
                }

                // if the ID isn't null, we need to be updating, not adding
                // ... also check for the case where the issue was deleted and subsequently saved
                if (issue.ID != null && FindIssue(issue.ID) != null)
                {
                    // go away, you dirty hobo
                    return UpdateIssue(actor, issue);
                }

                // look up our record on the user
                Data.User myActor = App.Instance().UManager.FindUser(actor.ID);

                if (myActor == null)
                {
                    // unknown user
                    return null;
                }

                // add an ID
                issue.ID = IncrementingCounter;

                // if not set, manually set the creation user, date
                if (issue.Creator == null)
                {
                    issue.Creator = myActor;
                }
                if (issue.CreationDate == DateTime.MinValue)
                {
                    issue.CreationDate = DateTime.Now;
                }

                // only let admins assign issues to users
                    // unless the user is assigning the issue to himself
                if (issue.AssignedTo != null &&
                    myActor.ID != issue.AssignedTo.ID && 
                    myActor.Access != Data.User.Privileges.Admin)
                {
                    issue.AssignedTo = null;
                }

                // add this issue to the dictionary
                Issues.Add(issue.ID, issue);

                // serialize the issue list
                Serialize();

                // we were successful
                return issue;
            }

            /// <summary>
            /// Look up an issue and find it in the dictionary
            /// </summary>
            /// <param name="name">The issue id to look up</param>
            /// <returns>the user instance for that user if found, null otherwise</returns>
            public Data.Issue FindIssue(Int64? id)
            {
                if (id == null)
                {
                    // I hate you.
                    return null;
                }

                //App.WriteLine("Looking up: " + id);
                if (issues.ContainsKey(id))
                    return issues[id];
                else
                    return null;
            }

            /// <summary>
            /// Allow a user to edit/update the issues assigned to them. 
            /// Editing should be done in a separate window. Editing by normal users 
            /// should be limited to updates on status, hours and description.
            /// </summary>
            /// <param name="actor"></param>
            /// <param name="issue"></param>
            public Data.Issue UpdateIssue(Data.User actor, Data.Issue issue)
            {
                // if the user passed in false, they're doing something wrong
                if (issue == null)
                {
                    return null;
                }

                // if the issue has a null ID, they need to be adding, not updating
                if (issue.ID == null)
                {
                    return AddIssue(actor, issue);
                }

                // get our record for the actor
                Data.User myActor = App.Instance().UManager.FindUser(actor.ID);

                // get our record for the issue
                Data.Issue myIssue = FindIssue(issue.ID);

                // if the issue isn't found, then it was probably deleted and we need to add it back
                if (myIssue == null)
                {
                    return AddIssue(actor, issue);
                }

                // only admins can reassign issues
                    // TODO: should users be able to assign issues to themselves?
                if (myActor.Access == Data.User.Privileges.Admin)
                {
                    myIssue.AssignedTo = issue.AssignedTo;
                    myIssue.Creator = issue.Creator;
                }

                // is the user an admin, the creator, or the assigned user?
                if (myActor.Access == Data.User.Privileges.Admin ||
                    (myIssue.AssignedTo != null && myIssue.AssignedTo.ID.Equals(actor.ID)) ||
                    myIssue.Creator.ID.Equals(actor.ID))
                {
                    myIssue.CreationDate = issue.CreationDate;
                    myIssue.DetailedDescription = issue.DetailedDescription;
                    myIssue.DueDate = issue.DueDate;
                    myIssue.HoursToGo = issue.HoursToGo;
                    myIssue.HoursWorked = issue.HoursWorked;
                    myIssue.IssueStatus = issue.IssueStatus;
                    myIssue.Name = issue.Name;

                    return myIssue;
                }
                else
                {
                    return null;
                }
            }

            /// <summary>
            /// Deletes an issue with a specific ID
            /// </summary>
            /// <param name="id"></param>
            public void Delete(Int64? id)
            {
                if (id == null)
                {
                    return;
                }

                App.WriteLine("Deleting: " + id);

                // look up a user by the provided name
                Data.Issue issue = FindIssue(id);

                // check if issue user exists
                if (issue != null)
                {
                    // remove it if it does
                    issues.Remove(id);

                    // serialize issues
                    Serialize();
                }
            }

            /// <summary>
            /// Serialize the list of issues. This method runs every time an issue is changed.
            /// </summary>
            public void Serialize()
            {
                XmlSerializer serializer = new XmlSerializer(typeof(IssueManager));
                string filename = "issues.xml";
                StreamWriter outStream = new StreamWriter(filename, false);
                serializer.Serialize(outStream, this);
                outStream.Close();
            }
        }
    }
}
